﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IndicoInterface;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using DeepZoomPivotConstructor.VisualCacheXMLDirectory;
using DeepZoomPivotConstructor.DTBuilderLib;
using System.ComponentModel.Composition;
using DeepZoomPivotConstructor.ErrorAndLoggingInterfaces;
using BuilderLibrary;
using DeepZoomPivotConstructor.VisualLib;
using RenderEngineInterfaces;
using DeepZoomPivotConstructor.VisualLib.Hotspots;
using DeepZoomPivotConstructor.VisualCacheInterfaces;
using System.Xml.Serialization;

namespace DeeptalkConverterLib
{
    /// <summary>
    /// Main class to control the render. Menat to be used from either the GUI or from
    /// the command line.
    /// </summary>
    public class RenderControl
    {
        private AgendaInfo _agenda { get; set; }
        private DirectoryInfo _renderDir { get; set; }

        /// <summary>
        /// The DPI we shoudl render all the images at.
        /// </summary>
        public int DPI { get; set; }

        /// <summary>
        /// Init everything
        /// </summary>
        /// <param name="agenda"></param>
        public void StartRender(AgendaInfo agenda, DirectoryInfo renderDir)
        {
            ///
            /// Save the arguments here...
            /// 

            _agenda = agenda;
            _renderDir = renderDir;

            ///
            /// Defaults for externals:
            /// 

            DPI = 150;

            ///
            /// Now, configure the rendering parameters. perhaps
            /// this will come from settings sometime...
            /// 

            DPIText = 150;
            XBoarderSize = 0.2;
            YBoarderSize = 0.2;

            TextHeight = 0.5;
            FileBackgroundColor = Color.Gray;

            MetaDataFilename = "RenderMetaData.xml";
            RenderDBDirectoryName = "SubImageRenderDB";

            ///
            /// We are ready to start this now!
            /// 

            _setWhenDoneWithRender.Reset();
            Task.Factory.StartNew(() => RunThisRender());
        }

        /// <summary>
        /// Starts the render in the background.
        /// </summary>
        /// <returns></returns>
        private void RunThisRender()
        {
            try
            {
                ///
                /// Infrastructure setup
                /// 

                ExternalToolLocations.LoadDefaultLocations();
                SetupMEF();

                ///
                /// Get some basic info about the agenda. If this fails this early, then
                /// we are in trouble! :-)
                /// 

                RenderJobMetaData jobMetaData = new RenderJobMetaData();
                {
                    var m = _agenda.GetNormalizedConferenceData();
                    jobMetaData.MeetingTitle = m.Title;
                    jobMetaData.MeetingStart = m.StartDate;
                    jobMetaData.MeetingEnd = m.EndDate;
                }

                ///
                /// Now, render the scene
                /// 

                Visual scene = RenderToVisual();
                if (scene == null)
                {
                    Trace.WriteLine("Failed to get anything from the rendering!");
                }

                ///
                /// Extract the rest of the meta-data
                /// 

                if (scene == null)
                {
                    jobMetaData.NumberOfTalks = 0;
                    jobMetaData.NumberOfUpdatedTalks = 0;
                }
                else
                {
                    jobMetaData.NumberOfTalks = scene.CountNumberOfComposableUnits();
                    jobMetaData.NumberOfUpdatedTalks = scene.CountNumberOfComposableUnits(v => v.GetType() != typeof(VisualItemPlaceHolder));
                }

                ///
                /// Now do the deep zoom construction. If there are no updated talks then we can skip this.
                /// WARNING: this means if just talk meta-data is updated (title, author, etc.) then no rendering will occur...
                ///

                if (jobMetaData.NumberOfUpdatedTalks != 0)
                {
                    DeepZoomBuilder dz = new DeepZoomBuilder(MyErrorLogger, VisualCache);
                    var dzLocation = dz.RenderVisual(new DirectoryInfo(_renderDir.FullName + "\\meeting"), scene);

                    ///
                    /// Finally, move over the various files we need to use this thing.
                    /// 

                    FileInfo startPage = WebSiteBuilder.CreateWebSite(_renderDir, scene.Info.Title + " - DeepTalk");
                    var shortcutFile = WebSiteBuilder.CreateLocalStart(_renderDir);
                }

                ///
                /// Write out the new job meta data file...
                /// 

                {
                    XmlSerializer ser = new XmlSerializer(typeof(RenderJobMetaData));
                    FileInfo metaDataFile = new FileInfo(_renderDir.FullName + "\\renderJobMetaData.xml");
                    using (var tw = metaDataFile.CreateText())
                    {
                        ser.Serialize(tw, jobMetaData);
                    }
                }

                ///
                /// Delete now that done. If we had a crash these will be left behind...
                /// 

                RSettings.RenderingTempDir.Delete(true);
            }
            catch (Exception e)
            {
                Trace.WriteLine("Error when running the render: " + e.Message + " - " + e.StackTrace);
            }
            finally
            {
                ///
                /// And the error file if we got that far
                /// 

                if (MyErrorLogger != null)
                {
                    MyErrorLogger.SetLogfileLocation(_renderDir);
                }

                ///
                /// Let everyone know we are outta here!
                /// 

                _setWhenDoneWithRender.Set();

            }
        }

        /// <summary>
        /// Renders the complete agenda to a visual.
        /// </summary>
        /// <returns></returns>
        private Visual RenderToVisual()
        {
            var t = Task<Visual>.Factory.StartNew(() =>
            {
                return MyRenderer.Render(_agenda);
            });
            Visual v = t.Result;
            if (v == null)
            {
                return v;
            }

            ///
            /// Create a nice title background slide to frame the whole thing
            /// 

            v = RenderATitleBackground(v, RSettings.RenderingTempDir);

            ///
            /// Now that we have a scene we can get the title, and set the location where we will be doing
            /// the rendering. If we alreay know the title, then don't switch over... so if the title changes
            /// the rendered directory will remain the same. Or if the user changes the directory...
            /// 

            string title = "test - output";
            if (v.Info != null && v.Info.Title != null && v.Info.Title.Length > 0)
            {
                title = v.Info.Title;
            }

            return v;
        }
        

        /// <summary>
        /// Add a background slide with a title on it.
        /// </summary>
        /// <param name="scene"></param>
        /// <returns></returns>
        private Visual RenderATitleBackground(Visual visual, DirectoryInfo tempDir)
        {
            ///
            /// Get the slides setup correctly
            /// 

            visual.ScaleToWidth(10.0);

            ///
            /// Build the title page
            /// 

            VisualTitleAndBackground title = new VisualTitleAndBackground(visual)
            {
                {TextHeight, visual.Info.Title, new HSActionLink(new Uri(_agenda.ConferenceUrl))}
            };
            title.ImageWritingDirectory = tempDir;

            return title;
        }

        /// <summary>
        /// Holds the container for MEF
        /// </summary>
        private CompositionContainer _container;

        /// <summary>
        /// Get MEF setup and configured so we can go after and load all the goodies we need.
        /// </summary>
        private void SetupMEF()
        {
            ///
            /// Get MEF setup so we can find our plug-ins, etc. and
            /// get a render engine going which will be responsible for generating
            /// the Visual tree and downloading files, etc.
            /// 

            var catalog = new AggregateCatalog();
            _container = new CompositionContainer(catalog);
            CompositionBatch batch = new CompositionBatch();

            batch.AddPart(new RenderEngine.LayoutHelper());
            VisualCache = new VisualCacheDirectoryDB(new DirectoryInfo(_renderDir.FullName + "\\" + RenderDBDirectoryName));
            batch.AddPart(this);
            batch.AddPart(new RenderEngine.RenderControl(VisualCache));

            ///
            /// First job - see if we can find this guy having been rendered before. If so, then
            /// we pick up the cache database left over from last time.
            /// 

            string baseBuild = null;
            baseBuild = _renderDir.FullName;

            ///
            /// Settings we will be passing down
            /// 

            RSettings = new RenderSettings();
            RSettings.DPI = DPI;
            RSettings.RenderingTempDir = new DirectoryInfo(Path.GetTempPath() + Path.DirectorySeparatorChar + Path.GetRandomFileName());
            RSettings.RenderingTempDir.Create();
            batch.AddPart(RSettings);

            ///
            /// Next, get all the directories that might have plug-ins.
            /// 

            foreach (var dir in ExternalToolLocations.RenderPluginSearchDirectories)
            {
                catalog.Catalogs.Add(new DirectoryCatalog(dir.FullName));
            }

            ///
            /// Ok -- ready to hook everyone up!
            /// 

            _container.Compose(batch);
        }

        private ManualResetEvent _setWhenDoneWithRender = new ManualResetEvent(false);

        /// <summary>
        /// Block until we are finished with the render.
        /// </summary>
        public void WaitForDone()
        {
            _setWhenDoneWithRender.WaitOne();
        }

        /// <summary>
        /// Where we will put the sub-meeting database for
        /// storage.
        /// </summary>
        private string RenderDBDirectoryName { get; set; }

        /// <summary>
        /// How many DPI are the text guys we insert going to be?
        /// </summary>
        private int DPIText { get; set; }

        /// <summary>
        /// How much space to leave around the boarder in X and Y.
        /// </summary>
        private double XBoarderSize { get; set; }
        private double YBoarderSize { get; set; }

        /// <summary>
        /// How many inches tall should we make the text?
        /// </summary>
        private double TextHeight { get; set; }

        /// <summary>
        /// What color should the background fill be?
        /// </summary>
        private Color FileBackgroundColor { get; set; }

        /// <summary>
        /// What is the name of the file we want to store the meta data in?
        /// </summary>
        public string MetaDataFilename { get; set; }

        /// <summary>
        /// The error logger we will use to show off where we are!
        /// </summary>
        [Import]
        public IErrorLogger MyErrorLogger { get; set; }

        /// <summary>
        /// The render engine doing all the work!
        /// </summary>
        [Import]
        public IRenderEngine MyRenderer { get; set; }

        /// <summary>
        /// The settings we are using
        /// </summary>
        private RenderSettings RSettings { get; set; }

        /// <summary>
        /// The visual data cache.
        /// </summary>
        private IVisualCacheDB VisualCache { get; set; }
    }
}
